package main

import (
	"encoding/json"
	"fmt"

	"github.com/hyperledger/fabric-chaincode-go/shim"
	"github.com/hyperledger/fabric-protos-go/peer"
)

// SmartContract provides functions for managing an Object
type SmartContract struct {
}

// Object describes basic details of what makes up a simple object
type Object struct {
	ID   string `json:"ID"`
	Hash string `json:"hash"`
}

// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data.
// InitLedger adds a base set of objects to the ledger
func (s *SmartContract) Init(stub shim.ChaincodeStubInterface) peer.Response {
	objects := []Object{
		{ID: "object_0", Hash: "ec13a4c02aaa0ef53893742d5bf7d14c960333e0275a9c6472774e85c9f502c5"},
	}

	for _, object := range objects {
		objectJSON, err := json.Marshal(object)
		if err != nil {
			return shim.Error("Object marshal error")
		}

		err = stub.PutState(object.ID, objectJSON)
		if err != nil {
			return shim.Error("failed to put to world state.")
		}
	}

	return shim.Success(nil)
}

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SmartContract) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
	// Extract the function and args from the transaction proposal
	fn, args := stub.GetFunctionAndParameters()

	var result string
	var err error
	if fn == "CreateObject" {
		err = CreateObject(stub, args[0], args[1])
	} else { // assume 'get' even if fn is nil
		result, err = ReadObject(stub, args[0])
	}
	if err != nil {
		return shim.Error(err.Error())
	}

	// Return the result as success payload
	return shim.Success([]byte(result))
}

// CreateObject issues a new object to the world state with given details.
func CreateObject(stub shim.ChaincodeStubInterface, id string, hash string) error {
	res, err := stub.GetState(id)
	if res != nil || err != nil {
		return fmt.Errorf("the object %s already exists", id)
	}

	object := Object{
		ID:   id,
		Hash: hash,
	}
	objectJSON, err := json.Marshal(object)
	if err != nil {
		return err
	}

	return stub.PutState(id, objectJSON)
}

// ReadObject returns the object stored in the world state with given id.
func ReadObject(stub shim.ChaincodeStubInterface, id string) (string, error) {
	objectJSON, err := stub.GetState(id)
	if err != nil {
		return "", fmt.Errorf("failed to read from world state: %v", err)
	}
	if objectJSON == nil {
		return "", fmt.Errorf("the object %s does not exist", id)
	}

	var object Object
	err = json.Unmarshal(objectJSON, &object)
	if err != nil {
		return "", err
	}

	return object.Hash, nil
}

// main function starts up the chaincode in the container during instantiate
func main() {
	if err := shim.Start(new(SmartContract)); err != nil {
		fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
	}
}
